#!/usr/bin/env dart
// ignore_for_file: avoid_print, constant_identifier_names

import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:dio/dio.dart';
import 'package:http_parser/http_parser.dart';

// --- 脚本配置常量 ---

/// 上传 Apk 的私有服务器相关配置
const SERVER_IP = '172.18.254.96';
const WAN_SERVER_IP = '172.18.254.96';

/// 企业微信 Webhook 相关配置
const WECHAT_WEBHOOK_URL = 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send';
const WECHAT_TOKEN = '7abe9e0e-d5a4-4938-af16-33a8a6987a0c';

/// 默认@的手机号列表
final List<String> atMobiles = ['19982626354'];

/// JSON编码器，用于格式化输出
const ENCODER = JsonEncoder.withIndent('  ');

// --- 全局改造 ---
// **关键改动 1**: 创建一个全局的、可多次监听的 stdin 广播流。
// stdin 本身是单订阅流，不能被多次监听。我们将其转换为按行分割的广播流，
// 这样脚本中的多个地方就可以安全地等待用户输入了。
late final Stream<String> _broadcastStdin;

/// 主入口函数
void main(List<String> args) async {
  // **关键改动 2**: 在 main 函数开始时初始化广播流。
  _broadcastStdin = stdin
      .transform(utf8.decoder)
      .transform(const LineSplitter())
      .asBroadcastStream();

  try {
    // 1. 解析参数并创建配置
    final config = await ScriptConfig.fromArgs(args);
    // 2. 创建并执行构建流程
    await BuildRunner(config).run();
  } catch (e, s) {
    Logger.error('脚本执行失败: $e');
    if (e is ArgumentError) {
      Logger.info('请使用 --help 或 -h 查看帮助文档: ./ci/build_ci.md');
    } else {
      Logger.info('堆栈信息:\n$s');
    }
    exit(1);
  }
}

/// 封装了脚本的所有配置项
class ScriptConfig {
  ScriptConfig({
    required this.channel,
    required this.appName,
    required this.launchDevices,
    required this.pkgId,
    required this.noDing,
    required this.profileName,
    required this.curDirName,
    required this.profile,
  });

  final String? channel;
  final String? appName;
  final List<String>? launchDevices;
  final String? pkgId;
  final bool noDing;
  final String? profileName;
  final String curDirName;
  final Profile? profile;

  static Future<ScriptConfig> fromArgs(List<String> args) async {
    if (args.contains('--help') || args.contains('-h')) {
      print('请查看文档：./ci/build_ci.md');
      exit(0);
    }

    final params = _parseArgsToMap(args);
    Logger.isVerbose = params.containsKey('--verbose') || params.containsKey('-v');

    final profileName = params['--profile'];
    final profile = await Profile.selectFromLocal(profileName);

    final channel = params['--channel'] ?? params['-c'] ?? profile?.chan;
    final appName = params['--app-name'] ?? params['-n'] ?? profile?.appName;
    final launchDevices = (params['--launch-app-devices'] ?? params['-ld'])?.split(',');
    final pkgId = params['--pkg'] ?? params['-p'] ?? await PkgInfo.pkgId;
    final noDing = params.containsKey('--no-ding') || params.containsKey('-nd');
    final curDirName = Directory.current.path.split(Platform.pathSeparator).last;

    return ScriptConfig(
      channel: channel,
      appName: appName,
      launchDevices: launchDevices,
      pkgId: pkgId,
      noDing: noDing,
      profileName: profileName,
      curDirName: curDirName,
      profile: profile,
    );
  }

  static Map<String, String?> _parseArgsToMap(List<String> args) {
    final params = <String, String?>{};
    for (var i = 0; i < args.length;) {
      final arg = args[i];
      if (arg.startsWith('-')) {
        if (i + 1 < args.length && !args[i + 1].startsWith('-')) {
          params[arg] = args[i + 1];
          i += 2;
        } else {
          params[arg] = null;
          i++;
        }
      } else {
        throw ArgumentError('无效的参数格式: $arg');
      }
    }
    return params;
  }
}

/// 负责执行整个构建和上传流程
class BuildRunner {
  BuildRunner(this.config);

  final ScriptConfig config;
  static const String apkPath = 'build/app/outputs/flutter-apk/app-arm64-v8a-release.apk';

  Future<void> run() async {
    await _checkPrerequisites();
    await _buildApk();
    await _processAndUploadApk();
  }

  Future<void> _checkPrerequisites() async {
    final isAtProjRoot = await File('pubspec.yaml').exists();
    if (!isAtProjRoot) {
      throw const FileSystemException('请在Flutter项目根目录下运行此脚本。', 'pubspec.yaml');
    }
    Logger.success('环境检查通过。');
  }

  Future<void> _buildApk() async {
    final startTime = DateTime.now();
    Logger.info('开始构建APK...');

    final flutterBin = _getFlutterSdkPath() ?? 'flutter';
    final buildArgs = [
      'build', 'apk', '--release', '--split-per-abi',
      if (config.channel != null) '--dart-define=APP_INSTALL_CHANNEL=${config.channel}',
      if (config.appName != null) '--dart-define=APP_NAME=${config.appName}',
    ];

    Logger.info('执行命令: $flutterBin ${buildArgs.join(' ')}');

    final process = await Process.start(flutterBin, buildArgs, runInShell: true);
    final stdoutSubscription = process.stdout.transform(utf8.decoder).listen(Logger.info);
    final stderrSubscription = process.stderr.transform(utf8.decoder).listen(Logger.error);

    final exitCode = await process.exitCode;
    await stdoutSubscription.cancel();
    await stderrSubscription.cancel();

    if (exitCode != 0) {
      throw ProcessException(flutterBin, buildArgs, 'Flutter构建失败，退出码: $exitCode', exitCode);
    }
    final duration = DateTime.now().difference(startTime);
    Logger.success('APK构建成功！耗时: ${duration.inSeconds}秒');
  }

  Future<void> _processAndUploadApk() async {
    final apkFile = File(apkPath);
    if (!await apkFile.exists()) {
      throw const FileSystemException('构建产物未找到', apkPath);
    }

    final updateLog = await _promptForUpdateLog();
    final uploadToPgyer = await _promptForUploadTarget();

    if (uploadToPgyer) {
      await _performPgyerUpload(apkFile, updateLog);
    } else {
      await _performCustomServerUpload(apkFile, updateLog);
    }
  }

  /// 获取Git日志并提示用户确认或编辑
  Future<String> _promptForUpdateLog() async {
    String suggestedLog = '常规优化';
    Logger.info('正在获取最新的 Git 提交日志...');

    try {
      final result = await Process.run(
        'git',
        ['log', '-1', '--pretty=%B'],
        stdoutEncoding: utf8,
        stderrEncoding: utf8,
      );
      if (result.exitCode == 0) {
        final log = result.stdout.toString().trim();
        if (log.isNotEmpty) {
          suggestedLog = log;
          Logger.success('成功获取到 Git 日志。');
        } else {
          Logger.error('获取到的 Git 日志为空，将使用默认值作为建议。');
        }
      } else {
        Logger.error('执行 git log 命令失败，将使用默认值作为建议。');
        Logger.error('错误信息: ${result.stderr}');
      }
    } catch (e) {
      Logger.error('获取 Git 日志时发生异常: $e');
      Logger.info('将使用默认值作为建议。');
    }

    Logger.info('');
    Logger.prompt('======== 以下是建议的更新日志 ========');
    Logger.info(suggestedLog);
    Logger.prompt('======================================');
    Logger.info('');

    // **关键改动 3**: 移除 Windows 平台的特殊处理，统一使用带超时的输入函数
    Logger.prompt('请直接回车以使用上述日志，或输入新的日志内容:', ln: false);
    const timeout = Duration(seconds: 30);
    final userInput = await _ioPromptWithTimeout(timeout);
    String finalLog;

    if (userInput == _TIMED_OUT) {
      Logger.info('\n输入超时，将使用建议的日志。');
      finalLog = suggestedLog;
    } else {
      finalLog = userInput.trim().isEmpty ? suggestedLog : userInput.trim();
    }

    Logger.success('最终确认的更新日志为:');
    Logger.info(finalLog);
    return finalLog;
  }

  /// 提示用户选择上传目标
  Future<bool> _promptForUploadTarget() async {
    Logger.prompt('是否上传到蒲公英 (y/n, 默认 y): ', ln: false);
    const timeout = Duration(seconds: 30);
    final input = (await _ioPromptWithTimeout(timeout)).toLowerCase().trim();

    if (input == _TIMED_OUT) {
      Logger.info('\n输入超时，将自动选择 "y"。');
      return true;
    }
    return input != 'n';
  }

  Future<void> _performPgyerUpload(File apkFile, String updateLog) async {
    Logger.info('开始上传到蒲公英...');
    final success = await AppUploader.uploadToPgyer(
      path: apkFile.path,
      updateDescription: updateLog,
    );
    if (success) {
      Logger.success('蒲公英上传流程已启动，请在蒲公英后台查看详情。');
    } else {
      Logger.error('上传到蒲公英失败。');
    }
  }

  Future<void> _performCustomServerUpload(File apkFile, String updateLog) async {
    final time = DateTime.now();
    final version = await PkgInfo.version ?? 'unknown';
    final newFileName = '${config.appName}-${_fmtTime(time)}-$version.apk';

    Logger.info('开始上传到自定义服务器...');
    await _uploadToCustomServer(newFileName, apkFile);
    Logger.success('上传到自定义服务器成功！');

    final size = await apkFile.length() / 1024 / 1024;
    final buildTime = _fmtTime(time);
    final remotePath = '/${config.curDirName}/${config.profile?.name}';
    final wanAppUrl = 'http://$WAN_SERVER_IP:5244$remotePath/$newFileName';

    final results = <String, dynamic>{
      '项目': config.appName ?? 'N/A',
      '版本': version,
      '大小': '${size.toStringAsFixed(2)}MB',
      '构建时间': buildTime,
      '更新日志': updateLog,
      '下载链接': '[内网](${Uri.encodeFull(wanAppUrl)})',
    };

    Logger.info('构建结果详情:');
    Logger.info(ENCODER.convert(results));
    await _sendWeChatNotification(results);
  }

  Future<void> _uploadToCustomServer(String newFileName, File apkFile) async {
    Logger.info('上传中 (约1m/s，请耐心等待)...');
    try {
      final resp = await Dio().post(
        'http://$SERVER_IP:8080/upload',
        queryParameters: {
          'filename': newFileName,
          'proj': config.curDirName,
          'dir': config.profile?.name,
        },
        data: FormData.fromMap({
          'file': MultipartFile.fromBytes(
            await apkFile.readAsBytes(),
            filename: newFileName,
            contentType: MediaType('application', 'vnd.android.package-archive'),
          ),
        }),
      );
      if (resp.statusCode != 200) {
        throw DioException(requestOptions: resp.requestOptions, response: resp, error: '上传失败: ${resp.data}');
      }
    } catch (e) {
      throw Exception('上传到自定义服务器失败: $e');
    }
  }

  Future<void> _sendWeChatNotification(Map<String, dynamic> data) async {
    if (config.noDing) {
      Logger.info('已通过 --no-ding 参数禁用企业微信通知。');
      return;
    }

    Logger.prompt('是否发送企业微信消息 (y/n, 默认 y): ', ln: false);
    const timeout = Duration(seconds: 30);
    final input = (await _ioPromptWithTimeout(timeout)).toLowerCase().trim();

    if (input == _TIMED_OUT) {
      Logger.info('\n输入超时，将自动选择 "y"。');
    } else if (input == 'n') {
      Logger.info('用户取消发送企业微信消息。');
      return;
    }

    Logger.info('正在发送企业微信通知...');
    await WeChat.send(
      title: config.appName ?? '构建结果',
      data: data,
      atMobiles: atMobiles,
    );
  }

  String? _getFlutterSdkPath() {
    final file = File('android/local.properties');
    if (!file.existsSync()) return null;
    final properties = file.readAsLinesSync();
    for (var line in properties) {
      if (line.startsWith('flutter.sdk=')) {
        return '${line.split('=')[1].trim()}${Platform.pathSeparator}bin${Platform.pathSeparator}flutter';
      }
    }
    return null;
  }
}

// --- 工具类和辅助函数 ---

const String _TIMED_OUT = '__TIMED_OUT__';

/// 从控制台读取用户输入，并带有倒计时功能。
Future<String> _ioPromptWithTimeout(Duration timeout) async {
  // **关键改动 4**: 从全局的广播流 `_broadcastStdin` 获取输入，而不是直接用 `stdin`
  final inputFuture = _broadcastStdin.first;
  final timeoutFuture = Future.delayed(timeout, () => _TIMED_OUT);

  final countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
    final remaining = timeout.inSeconds - timer.tick + 1;
    if (remaining > 0) {
      stdout.write('\r将在 ${remaining} 秒后自动确认... ');
    } else {
      stdout.write('\r                         \r');
    }
  });

  // 让输入和超时进行“竞争”
  final result = await Future.any([inputFuture, timeoutFuture]);
  countdownTimer.cancel();
  // 清理倒计时残留的行
  stdout.write('\r' + ' ' * 50 + '\r');

  // 如果结果是超时标志，就返回它，否则返回用户输入
  if (result is String) {
    return result;
  }
  // 在极少数情况下，如果Future.any返回的不是预期的类型，提供一个安全的回退
  return '';
}

/// 简单的日志工具类
abstract final class Logger {
  static const String _reset = '\x1b[0m';
  static const String _green = '\x1b[32m';
  static const String _red = '\x1b[31m';
  static const String _yellow = '\x1b[33m';
  static bool isVerbose = false;

  static void info(Object? obj, {bool needVerbose = false}) {
    if (!isVerbose && needVerbose) return;
    stdout.writeln(obj);
  }

  static void success(Object? obj) {
    stdout.writeln('$_green$obj$_reset');
  }

  static void error(Object? obj) {
    stderr.writeln('$_red$obj$_reset');
  }

  static void prompt(String message, {bool ln = true}) {
    final coloredMessage = '$_yellow$message$_reset';
    if (ln) {
      stdout.writeln(coloredMessage);
    } else {
      stdout.write(coloredMessage);
    }
  }
}

// **关键改动 5**: 移除不再使用且有问题的 _powerShellWrite 函数
// Future<String?> _powerShellWrite... (整个函数被删除)

String _fmtTime(DateTime time) {
  return '${time.year}-${time.month.toString().padLeft(2, '0')}-${time.day.toString().padLeft(2, '0')}-${time.hour.toString().padLeft(2, '0')}${time.minute.toString().padLeft(2, '0')}${time.second.toString().padLeft(2, '0')}';
}

/// 企业微信通知模块
abstract final class WeChat {
  static final _dio = Dio(BaseOptions(
    headers: {'Content-Type': 'application/json'},
    validateStatus: (status) => status != null && status < 500,
  ));

  static Future<void> send({
    required String title,
    required Map<String, dynamic> data,
    List<String>? atMobiles,
  }) async {
    if (WECHAT_WEBHOOK_URL.isEmpty || WECHAT_TOKEN.isEmpty) {
      Logger.error('企业微信的 URL 或 Token 未配置。');
      return;
    }
    try {
      final resp = await _dio.post(
        WECHAT_WEBHOOK_URL,
        data: {
          'msgtype': 'markdown',
          'markdown': {
            'content': _toMarkdown(title, data),
            'mentioned_mobile_list': atMobiles,
          },
        },
        queryParameters: {'key': WECHAT_TOKEN},
      );
      if (resp.statusCode == 200 && resp.data['errcode'] == 0) {
        Logger.success('企业微信消息发送成功！');
      } else {
        throw Exception('发送失败: ${resp.statusCode}, ${resp.data}');
      }
    } catch (e) {
      Logger.error('发送企业微信消息时发生网络错误: $e');
    }
  }

  static String _toMarkdown(String title, Map<String, dynamic> data) {
    final buf = StringBuffer();
    buf.writeln('## $title\n');
    for (final entry in data.entries) {
      buf.writeln('> **${entry.key}**: ${entry.value}');
    }
    return buf.toString();
  }
}

/// IDEA 运行配置(.run/*.xml)解析
final class Profile {
  const Profile({required this.name, this.chan, this.appName});
  final String name;
  final String? chan;
  final String? appName;

  static final _nameReg = RegExp(r'<configuration .*name="([^"]+)"');
  static final _argsReg = RegExp(r'<option name="additionalArgs" value="([^"]+)"');

  static Future<Profile?> selectFromLocal(String? profileName) async {
    final dir = Directory('.run');
    if (!await dir.exists()) return null;

    final files = await dir.list().where((f) => f.path.endsWith('.xml')).toList();
    if (files.isEmpty) return null;

    final profiles = files.map((f) => fromFile(File(f.path))).whereType<Profile>().toList();
    if (profiles.isEmpty) {
      Logger.info('在 .run/ 目录中未找到有效的配置文件。');
      return null;
    }
    if (profiles.length == 1) return profiles.first;

    if (profileName != null) {
      return profiles.firstWhere((p) => p.name == profileName, orElse: () {
        Logger.error('未找到名为 "$profileName" 的配置。');
        throw ArgumentError('无效的profile名称');
      });
    }

    Logger.prompt('检测到多个运行配置，请选择一个:');
    for (var i = 0; i < profiles.length; i++) {
      Logger.info('$i: ${profiles[i].name}');
    }
    Logger.prompt('请输入序号 (默认 0): ', ln: false);

    const timeout = Duration(seconds: 30);
    final userInput = await _ioPromptWithTimeout(timeout);
    int index;

    if (userInput == _TIMED_OUT) {
      Logger.info('\n输入超时，将自动选择 "0: ${profiles[0].name}"。');
      index = 0;
    } else {
      index = int.tryParse(userInput) ?? 0;
    }

    if (index < 0 || index >= profiles.length) {
      Logger.error('无效的选项索引，将使用默认选项 0。');
      index = 0;
    }
    return profiles[index];
  }

  static Profile? fromFile(File file) {
    final content = file.readAsStringSync();
    final name = _nameReg.firstMatch(content)?.group(1);
    final args = _argsReg.firstMatch(content)?.group(1);
    if (name == null || args == null) return null;

    final argsMap = <String, String>{};
    final defines = RegExp(r'--dart-define=(\S+?)=([\S_]+)').allMatches(args);
    for (final match in defines) {
      argsMap[match.group(1)!] = match.group(2)!;
    }
    return Profile(
      name: name,
      chan: argsMap['APP_INSTALL_CHANNEL'],
      appName: argsMap['APP_NAME'],
    );
  }
}

/// 应用包信息获取
abstract final class PkgInfo {
  static final _idReg = RegExp(r'applicationId\s+"([^"]+)"');
  static final _versionReg = RegExp(r'version: (\d+\.\d+\.\d+.*)');

  static Future<String?> get pkgId async {
    final file = File('android/app/build.gradle');
    if (!await file.exists()) return null;
    final content = await file.readAsString();
    return _idReg.firstMatch(content)?.group(1);
  }

  static Future<String?> get version async {
    final file = File('pubspec.yaml');
    if (!await file.exists()) return null;
    final content = await file.readAsString();
    return _versionReg.firstMatch(content)?.group(1);
  }
}

/// 蒲公英上传模块
class AppUploader {
  static const String _pgyerApiKey = '51703a1d7b85abc64a6727ab4a9d7812';
  static final _dio = Dio(BaseOptions(
    baseUrl: 'https://www.pgyer.com/apiv2/',
    headers: {'Content-Type': 'multipart/form-data'},
  ));

  static Future<bool> uploadToPgyer({
    required String path,
    String? updateDescription,
  }) async {
    try {
      Logger.info('正在向蒲公英获取上传凭证...');
      final cosTokenData = await _getCOSToken(updateDescription);
      final uploadUrl = cosTokenData['endpoint'] as String;
      final params = cosTokenData['params'] as Map<String, dynamic>;

      Logger.info('凭证获取成功，开始上传文件至蒲公英...');
      await _uploadFile(uploadUrl, path, params);
      Logger.success('文件上传完成，正在等待蒲公英处理...');
      await _pollBuildInfo(params['key'] as String);
      return true;
    } catch (e) {
      Logger.error('蒲公英上传过程中出错: $e');
      return false;
    }
  }

  static Future<Map<String, dynamic>> _getCOSToken(String? updateDescription) async {
    final response = await _dio.post('app/getCOSToken', data: FormData.fromMap({
      '_api_key': _pgyerApiKey,
      'buildType': 'android',
      'buildInstallType': 1,
      if (updateDescription != null) 'buildUpdateDescription': updateDescription,
    }));
    if (response.statusCode == 200 && response.data['code'] == 0) {
      return response.data['data'] as Map<String, dynamic>;
    }
    throw Exception('获取蒲公英COSToken失败: ${response.data}');
  }

  static Future<void> _uploadFile(String url, String path, Map<String, dynamic> params) async {
    final file = File(path);
    final formData = FormData.fromMap({
      ...params,
      'file': await MultipartFile.fromFile(
        file.path,
        filename: file.path.split(Platform.pathSeparator).last,
      ),
    });

    final response = await Dio().post(url, data: formData, onSendProgress: (sent, total) {
      final percentage = (sent / total * 100).toStringAsFixed(1);
      stdout.write('\r上传进度: $percentage%');
    });
    stdout.writeln();
    if (response.statusCode != 204) {
      throw Exception('上传文件到蒲公英COS失败, HTTP status: ${response.statusCode}');
    }
  }

  static Future<void> _pollBuildInfo(String buildKey, {int maxRetries = 10, int currentRetry = 0}) async {
    if (currentRetry >= maxRetries) {
      throw Exception('获取构建信息超时，请稍后前往蒲公英网站查看。');
    }
    await Future.delayed(const Duration(seconds: 3));
    Logger.info('正在查询构建状态 (第 ${currentRetry + 1} 次)...');

    final response = await _dio.post('app/buildInfo', data: FormData.fromMap({
      '_api_key': _pgyerApiKey,
      'buildKey': buildKey,
    }));

    if (response.statusCode == 200 && response.data['code'] == 0) {
      final buildData = response.data['data'];
      Logger.success('蒲公英处理完成！');
      Logger.info('应用信息:');
      Logger.info(ENCODER.convert(buildData));
      Logger.info('下载短链接: https://www.pgyer.com/${buildData['buildShortcutUrl']}');
    } else if (response.data['code'] == 1247 || response.data['code'] == 1246) {
      await _pollBuildInfo(buildKey, maxRetries: maxRetries, currentRetry: currentRetry + 1);
    } else {
      throw Exception('获取构建信息失败: ${response.data}');
    }
  }
}